package main

import (
	"context"
	"crypto/ecdsa"
	"flag"
	"fmt"
	wsApi "go-eth-tracker/api"
	wsAppConfig "go-eth-tracker/config"
	wsDB "go-eth-tracker/dao"
	"go-eth-tracker/hdwallet"
	"log"
	"math"
	"math/big"
	"net/url"
	"os"
	"runtime"
	"time"

	"go.uber.org/zap"

	_ "github.com/denisenkom/go-mssqldb"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"github.com/jmoiron/sqlx"
	"golang.org/x/crypto/sha3"

	"github.com/gin-contrib/zap"
	"github.com/spf13/viper"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var zapLogger *zap.Logger

var (
	port      = flag.Int("p", 8090, "run port")
	appConfig *wsAppConfig.AppConfig
)

func init() {
	initViperConfig()
	initZapLog()
}

func initViperConfig() {
	viper.SetConfigName("config.yml") // name of config.yml file (without extension)
	viper.SetConfigType("yaml")       // REQUIRED if the config.yml file does not have the extension in the name
	viper.AddConfigPath(".")          // optionally look for config.yml in the working directory
	err := viper.ReadInConfig()       // Find and read the config.yml file
	if err != nil {                   // Handle errors reading the config.yml file
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			panic(fmt.Errorf("未找到配置文件 config.yml: %s \n", err))
		} else {
			panic(fmt.Errorf("配置文件 config.yml 解析错误: %s \n", err))
		}
	}

	//viper.WatchConfig()
	//viper.OnConfigChange(func(e fsnotify.Event) {
	//	fmt.Println("Config file changed:", e.Name)
	//})

	tmpAppConfig := &wsAppConfig.AppConfig{}
	viper.Unmarshal(tmpAppConfig)

	appConfig = tmpAppConfig
}

func initZapLog() {
	fileWriteSync := zapcore.AddSync(&lumberjack.Logger{
		Filename:   appConfig.Log.Filename,
		MaxSize:    appConfig.Log.MaxSize, // megabytes
		MaxBackups: appConfig.Log.MaxBackups,
		MaxAge:     appConfig.Log.MaxAge, // days
	})
	stdoutWriteSync := zapcore.AddSync(os.Stdout)

	encoder := zap.NewProductionEncoderConfig()
	encoder.EncodeTime = zapcore.RFC3339TimeEncoder
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(encoder),
		zapcore.NewMultiWriteSyncer(fileWriteSync, stdoutWriteSync),
		zap.InfoLevel,
	)
	zapLogger = zap.New(core,
		zap.AddCaller(),
		zap.AddStacktrace(zap.ErrorLevel))
}

func main() {
	flag.Parse()
	runtime.GOMAXPROCS(runtime.NumCPU())

	// viper.BindFlagValues(flag.CommandLine)
	if appConfig.Server.Port > 0 {
		port = &appConfig.Server.Port
	}

	connectionString := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s&connection+timeout=%d",
		url.QueryEscape(appConfig.Mssql.DBUser),
		url.QueryEscape(appConfig.Mssql.DBPassword),
		appConfig.Mssql.Server,
		appConfig.Mssql.DBName,
		appConfig.Mssql.ConnectionTimeout)
	if !appConfig.Mssql.Encrypt {
		connectionString += "&encrypt=disable"
	}

	fmt.Println(connectionString)

	sqlxDB, err := sqlx.Connect("mssql", connectionString)
	if err != nil {
		zapLogger.DPanic(fmt.Sprintf(" Error open dao: %s\n", err.Error()))
		return
	}
	defer sqlxDB.Close()

	// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
	sqlxDB.SetMaxIdleConns(10)
	// SetMaxOpenConns sets the maximum number of open connections to the database.
	sqlxDB.SetMaxOpenConns(100)
	// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
	sqlxDB.SetConnMaxLifetime(time.Hour)

	mssqlDao := &wsDB.MssqlDao{
		DB: sqlxDB,
		LogContext: zapLogger,
	}

	apiContext := &wsApi.Context{
		Merchants: make(map[string]*wsApi.MerchantContext),
		MssqlDao:  mssqlDao,
	}

	generalHandler := &wsApi.GeneralHandler{
		MssqlDao: mssqlDao,
		LogContext: zapLogger,
		ApiContext: apiContext,
		AppConfig: appConfig,
	}

	gin.SetMode(gin.ReleaseMode)
	router := gin.New()

	// Add a ginzap middleware, which:
	//   - Logs all requests, like a combined access and error log.
	//   - Logs to stdout.
	//   - RFC3339 with UTC time format.
	router.Use(ginzap.Ginzap(zapLogger, time.RFC3339, true))

	// Logs all panic to error log
	//   - stack means whether output the stack info.
	router.Use(ginzap.RecoveryWithZap(zapLogger, true))

	outApi := router.Group("/outapi")
	outApiV1 := outApi.Group("/v1")

	outApiV1.GET("/gate", generalHandler.Dispatch)
	outApiV1.POST("/gate", generalHandler.Dispatch)

	client, err := ethclient.Dial(appConfig.EthClient.RpcUrl)
	if err != nil {
		zapLogger.Fatal(fmt.Sprintf("ETH 连接建立失败：%s", err.Error()))
	}

	zapLogger.Info(fmt.Sprintf("ETH 连接已建立在 %s", appConfig.EthClient.RpcUrl))

	account := common.HexToAddress("0x90dEd5022aB93986987dD427060C3550AF4a43b5")
	balance, err := client.BalanceAt(context.Background(), account, nil)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(balance)

	fbalance := new(big.Float)
	fbalance.SetString(balance.String())
	ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))

	fmt.Println(ethValue)

	mnenonicWords := "bread swim patient sponsor route enroll senior ivory worry turtle start wing"
	wallet, err := hdwallet.NewFromMnemonic(mnenonicWords)
	if err != nil {
		fmt.Println(err)
	}

	path, err := hdwallet.ParseDerivationPath("m/44'/60'/0'/0/0")
	if err != nil {
		fmt.Println(err)
	}

	accounts, err := wallet.Derive(path, false)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(accounts.Address.Hex())

	fmt.Println(fmt.Sprintf("Server Listening on %s:%d", appConfig.Server.Bind, *port))
	router.Run(fmt.Sprintf("%s:%d", appConfig.Server.Bind, *port))
	return

	privateKey, err := crypto.HexToECDSA("766d99d405a89907198dc5a3464c3c19d5f5d2b02613af328f93d79367bf57ae")
	if err != nil {
		log.Fatal(err)
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		log.Fatal("error casting public key to ECDSA")
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("from:", fromAddress.Hex())

	zeroValue := big.NewInt(0)
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	toAddress := common.HexToAddress("0x90dEd5022aB93986987dD427060C3550AF4a43b5")
	tokenAddress := common.HexToAddress("0xc9015F917B3eFf71A65beE842Dd37bf253524C32")
	transferFnSignature := []byte("transfer(address,uint256)")
	hash := sha3.NewLegacyKeccak256()
	hash.Write(transferFnSignature)
	methodID := hash.Sum(nil)[:4]
	fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb

	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
	fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d

	amount := new(big.Int)
	amount.SetString("2", 10) // 2 tokens
	paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
	fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000

	var data []byte
	data = append(data, methodID...)
	data = append(data, paddedAddress...)
	data = append(data, paddedAmount...)

	gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
		To:   &toAddress,
		Data: data,
	})
	if err != nil {
		log.Fatal(err)
	}

	gasLimit = 6721975

	fmt.Println(gasLimit) // 23256

	tx2 := types.NewTransaction(nonce, tokenAddress, zeroValue, gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("chain-id:", chainID)

	signedTx, err := types.SignTx(tx2, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		log.Fatal(err)
	}

	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		log.Fatal(err)
	}

	//infuraWsClient, err := ethclient.Dial("wss://mainnet.infura.io/ws/v3/1eef7be6bfba4bee895e1925fe6d68be")
	//if err != nil {
	//	log.Fatal(err)
	//}
	//
	////contractAddress := common.HexToAddress("0x9C3Dad5B1b04fb234B7bB41b9b3B4c7194CdB20A")
	////query := ethereum.FilterQuery{
	////	Addresses: []common.Address{contractAddress},
	////}
	////
	////logs := make(chan types.Log)
	////sub, err := infuraWsClient.SubscribeFilterLogs(context.Background(), query, logs)
	////if err != nil {
	////	log.Fatal(err)
	////}
	////
	////for {
	////	select {
	////	case err := <-sub.Err():
	////		log.Fatal(err)
	////	case vLog := <-logs:
	////		fmt.Println(vLog) // pointer to event log
	////	}
	////}
	//
	//headers := make(chan *types.Header)
	//// sub, err := infuraWsClient.SubscribeFilterLogs(context.Background(), query, logs)
	//sub, err := infuraWsClient.SubscribeNewHead(context.Background(), headers)
	//if err != nil {
	//	log.Fatal(err)
	//}
	//
	//for {
	//	select {
	//	case err := <-sub.Err():
	//		log.Fatal(err)
	//	case vHeader := <-headers:
	//		header := *vHeader
	//		fmt.Println(fmt.Sprintf("%v (%v)", header.TxHash.Hex(), header.Number))
	//
	//		blk, err := infuraWsClient.BlockByNumber(context.Background(), header.Number)
	//		if err != nil {
	//			fmt.Printf("Cannot get block info by number: %v\n", header.Number)
	//			continue
	//		}
	//
	//		fmt.Printf("%v transations in block number: %v\n", len(blk.Transactions()), header.Number)
	//	}
	//}
}
